package com.foogui.utils;

import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Component
public class SimpleRedisLock implements ILock {

    public static final DefaultRedisScript<Long> LOCK_SCRIPT;

    // 读取Lua脚本，确保类加载时就完成初始化DefaultRedisScript对象
    static {
        LOCK_SCRIPT = new DefaultRedisScript<>();
        LOCK_SCRIPT.setLocation(new ClassPathResource("lock.lua"));
        LOCK_SCRIPT.setResultType(Long.class);
    }
    // 区分不同jvm线程相同Id的线程，避免释放其他线程持有的锁
    // 每个jvm只加载一次静态属性uuid，保证了同一个jvm所有线程使用的都是同一个uuid
    public static final String uuid = UUID.randomUUID().toString(true) + "-";


    //定义锁的前缀
    public static final String LOCK_KEY_PREFIX = "lock:";

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 获取锁，redis中 key为 lock:业务标识:锁名，value为uuid+线程id，避免分布式环境下不同线程出现id相同情况
     * @param duration 锁最长持有时间，过期自动释放，避免占内存
     * @param lockName 锁名
     * @return
     */
    @Override
    public boolean tryLock(String lockName, Long duration, TimeUnit timeUnit) {
        long id = Thread.currentThread().getId();
        Boolean res = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY_PREFIX + lockName, uuid + id, duration, timeUnit);
        //避免res为空出现拆箱空指针的问题
        return Boolean.TRUE.equals(res);
    }

    /**
     * 线程只能释放自己的锁，不能释放他人的，不能单纯的 delete key
     * @param lockName 锁名
     */
    @Override
    public boolean unlock(String lockName) {
        String threadId = uuid + Thread.currentThread().getId();
        // 获取value，值是uuid+线程id
        // String value = stringRedisTemplate.opsForValue().get(LOCK_KEY_PREFIX + lockName);
        //判断是否是当前线程持有锁
        //这里存在原子性的问题，应该写到lua脚本中，必须保证判断和释放锁是原子性的
        // if (threadId.equals(value)){
        //     stringRedisTemplate.delete(LOCK_KEY_PREFIX + name);
        // }
        // lua保证了读写的原子性
        Long isUnLock = stringRedisTemplate.execute(LOCK_SCRIPT,
                Collections.singletonList(LOCK_KEY_PREFIX + lockName),  // 生成只有一个元素的List
                threadId);
        return "1".equals(isUnLock+"");

    }
}
